﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace ColorWheelNET
{
  //  [ToolboxBitmap(typeof(ColorWheel))]
     [ToolboxBitmap(@"Q:\\ColorWheel\\ColorWheel\\Resources\\HSVTriangle.png")]
    //[ToolboxBitmap(@"Resources\\HSVTriangle.png")]
    public partial class ColorWheel : UserControl, IDisposable
    {
        #region Const

        const int _BUFFER = 2;
        const int _SLIDER_TOP_OFFSET = 10;
        const int _SLIDER_HEIGHT_OFFSET = 21;
        const double _LIGHTNESS_FORGIVENESS = 3;

        #endregion

        #region Custom Events

        public delegate void SelectedColorChangedHandler(object value, SelectedColorChangedEventArgs eventArgs);
        public event SelectedColorChangedHandler SelectedColorChanged;


        public delegate void SelectedColorChangingHandler(object value, SelectedColorChangingEventArgs eventArgs);
        public event SelectedColorChangingHandler SelectedColorChanging;

        #endregion

        /// <summary>
        /// Gets or Sets the Style of the ColorWheel
        /// </summary>
        public ColorWheelDisplayStyle DisplayStyle
        {
            get { return cw.DisplayStyle; }
            set 
            {
                if (value == ColorWheelDisplayStyle.HSVTriangle || value == ColorWheelDisplayStyle.Rectangle)
                {
                    pnlLightSlider.Visible = false;
                }
                else
                {
                    pnlLightSlider.Visible = true;
                }
                cw.DisplayStyle = value;
            }
        }

        private bool LightnessVisible
        {
            get { return pnlLightSlider.Visible; }
            set { pnlLightSlider.Visible = value; }
        }

         /// <summary>
         /// Constructor for the ColorWheel control object.
         /// </summary>
        public ColorWheel()
        {
            InitializeComponent();

            //Events TO Passthrough!
            cw.MouseClick += cw_MouseClick;
            cw.MouseDoubleClick += cw_MouseDoubleClick;
            cw.MouseDown += cw_MouseDown;
            cw.MouseHover += cw_MouseHover;
            cw.MouseMove += cw_MouseMove;
            cw.MouseUp += cw_MouseUp;
            cw.MouseWheel += cw_MouseWheel;
            pnlLightSlider.MouseClick += cw_MouseClick;
            pnlLightSlider.MouseDoubleClick += cw_MouseDoubleClick;
            pnlLightSlider.MouseDown += cw_MouseDown;
            pnlLightSlider.MouseHover += cw_MouseHover;
            pnlLightSlider.MouseMove += cw_MouseMove;
            pnlLightSlider.MouseUp += cw_MouseUp;
            pnlLightSlider.MouseWheel += cw_MouseWheel;

            pnlAlphaSlider.MouseClick += cw_MouseClick;
            pnlAlphaSlider.MouseDoubleClick += cw_MouseDoubleClick;
            pnlAlphaSlider.MouseDown += cw_MouseDown;
            pnlAlphaSlider.MouseHover += cw_MouseHover;
            pnlAlphaSlider.MouseMove += cw_MouseMove;
            pnlAlphaSlider.MouseUp += cw_MouseUp;
            pnlAlphaSlider.MouseWheel += cw_MouseWheel;


            cw.SelectedColorChanged += cw_SelectedColorChanged;
            cw.SelectedColorChanging += cw_SelectedColorChanging;

            pnlLightness.Paint += new PaintEventHandler(pnlLightness_Paint);

            pnlSelectedColor.Paint += new PaintEventHandler(pnlSelectedColor_Paint);

            trkLightness.ValueChanged += new EventHandler(trkLightness_ValueChanged);

            txtA.TextChanged += txtRGB_TextChanged;
            txtR.TextChanged += txtRGB_TextChanged;
            txtG.TextChanged += txtRGB_TextChanged;
            txtB.TextChanged += txtRGB_TextChanged;
            txtHex.TextChanged += txtRGB_TextChanged;
            txtH.TextChanged += txtRGB_TextChanged;
            txtS.TextChanged += txtRGB_TextChanged;
            txtV.TextChanged += txtRGB_TextChanged;
        }

        private bool _suppressTextChanged = false;

        void txtRGB_TextChanged(object sender, EventArgs e)
        {
            if (_suppressTextChanged || sender == null || this.IsDisposed || this.Disposing) { return; }
            if ((sender as TextBox).Text.Trim() == string.Empty) { return; }

            try
            {
                _suppressTextChanged = true;

                if (sender == txtA || sender == txtR || sender == txtG || sender == txtB)
                {
                    int a, r, g, b;
                    if (int.TryParse(txtA.Text.Trim(), out a) &&
                        int.TryParse(txtR.Text.Trim(), out r) &&
                        int.TryParse(txtG.Text.Trim(), out g) &&
                        int.TryParse(txtB.Text.Trim(), out b))
                    {
                        if ((a >= 0 && a <= 255) &&
                            (r >= 0 && r <= 255) &&
                            (g >= 0 && g <= 255) &&
                            (b >= 0 && b <= 255))
                        {
                            this.Color = Color.FromArgb(a, r, g, b);
                            UpdateHexTextBox();
                            UpdateHsvTextBox();
                        }
                    }
                }

                else if (sender == txtHex && txtHex.Text.Length == 7)
                {
                    try
                    {
                        this.Color = ColorTranslator.FromHtml(txtHex.Text.Trim());
                        UpdateArgbTextBox();
                        UpdateHsvTextBox();
                    }
                    catch
                    {
                        return;
                    }
                }

                else if (sender == txtH || sender == txtS || sender == txtV)
                {
                    double h, s, v;
                    if (double.TryParse(txtH.Text.Trim(), out h) &&
                        double.TryParse(txtS.Text.Trim(), out s) &&
                        double.TryParse(txtV.Text.Trim(), out v))
                    {
                        if ((h >= 0 && h <= 360) &&
                            (s >= 0 && s <= 1) &&
                            (v >= 0 && v <= 1))
                        {
                            this.Color = ColorHelper.ColorFromHSV(h, s, v);
                            UpdateArgbTextBox();
                            UpdateHexTextBox();
                        }
                    }
                }
            }
            finally
            {
                _suppressTextChanged = false;
            }
        }

        void UpdateArgbTextBox()
        {
            txtA.Text = this.Color.A.ToString();
            txtR.Text = this.Color.R.ToString();
            txtG.Text = this.Color.G.ToString();
            txtB.Text = this.Color.B.ToString();
        }

        void UpdateHexTextBox()
        {
            txtHex.Text = ColorTranslator.ToHtml(this.Color);
        }

        void UpdateHsvTextBox()
        {
            double h, s, v;
            ColorHelper.HSVFromRGB(this.Color, out h, out s, out v);
            txtH.Text = string.Format("{0:0.00}", h);
            txtS.Text = string.Format("{0:0.00}", s);
            txtV.Text = string.Format("{0:0.00}", v);
        }


        void pnlSelectedColor_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            using (SolidBrush b = new SolidBrush(this.Color))
            {
                g.FillRectangle(b, pnlSelectedColor.ClientRectangle);
            }
        }

        void trkLightness_ValueChanged(object sender, EventArgs e)
        {
            Color lightnessColor = cw.ColorGroup;
            int half = (int)(trkLightness.Maximum / 2);

            if (this.cw.ColorGroup == Color.White)
            {
                //check for shades of gray!
                if (trkLightness.Value < (half - _LIGHTNESS_FORGIVENESS))
                {
                    double lightness = (double)trkLightness.Value / (half - _LIGHTNESS_FORGIVENESS);
                    int rgb = (int)(255 * lightness);
                    lightnessColor = Color.FromArgb(cw.Color.A, rgb, rgb, rgb);
                }
                else
                {
                    lightnessColor = Color.White;
                }
            }
            else if (!lightnessColor.IsEmpty)
            {
                double hue, saturation, value;
                ColorHelper.HSVFromRGB(lightnessColor, out hue, out saturation, out value);

                if (trkLightness.Value < half)//make darker
                {
                    value = (double)trkLightness.Value / (half - _LIGHTNESS_FORGIVENESS);//(double)trkLightness.Maximum;
                    value = value <= 1.0 ? value : 1.0;
                }
                else //make ligher
                {
                    saturation = ((double)(trkLightness.Maximum - trkLightness.Value) / half);
                }

                lightnessColor = ColorHelper.ColorFromHSV(hue, saturation, value);//, true); //HSL overload that figures out grays
            }
            this.LightnessColor = lightnessColor;
            SetColors(false);
        }

        private void trkAlpha_ValueChanged(object sender, EventArgs e)
        {
            SetAlphaColor();
            SetColors(false);
        }

        private void pnlAlpha_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.SmoothingMode = cw.SmoothingMode;
            try
            {
                if (this.Color.IsEmpty) { g.Clear(pnlAlpha.BackColor); return; }

                Color start = Color.FromArgb(255, this.LightnessColor.R, this.LightnessColor.G, this.LightnessColor.B);
                Color end = Color.FromArgb(0, this.LightnessColor.R, this.LightnessColor.G, this.LightnessColor.B);

                using (LinearGradientBrush b = new LinearGradientBrush(pnlAlpha.ClientRectangle, start, end, LinearGradientMode.Vertical))
                {
                    g.FillRectangle(b, pnlAlpha.ClientRectangle);
                }
            }
            catch
            {
                g.Clear(pnlAlpha.BackColor);
            }
        }

        void pnlLightness_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.SmoothingMode = cw.SmoothingMode;
            try
            {
                if (this.Color.IsEmpty) { g.Clear(pnlLightness.BackColor); return; }

                using (LinearGradientBrush b = new LinearGradientBrush(pnlLightness.ClientRectangle, cw.ColorGroup, cw.Color, 90f))
                {
                               
                        double hue, saturation, value;
                        ColorHelper.HSVFromRGB(cw.ColorGroup, out hue, out saturation, out value);

                        Color endColor = ColorHelper.ColorFromHSV(hue, saturation, 0.0);
                        Color startColor = ColorHelper.ColorFromHSV(hue, saturation, 1.0);

                    ColorBlend blend = new ColorBlend();
                    blend.Colors = new Color[]
                    {
                        Color.White,
                        cw.ColorGroup,
                        endColor,
                    };

                    blend.Positions = new float[]
                    {
                        0f,
                        2/3f,
                        1f,
                    };

                    b.InterpolationColors = blend;
                    g.FillRectangle(b, pnlLightness.ClientRectangle);
                }
            }
            catch
            {
                g.Clear(pnlLightness.BackColor);
            }
        }


        void cw_SelectedColorChanging(object value, SelectedColorChangingEventArgs e)
        {
            if (this.SelectedColorChanging != null)
            {
                SelectedColorChanging(this, e);
            }
        }


        
        void cw_SelectedColorChanged(object value, SelectedColorChangedEventArgs e)
        {
            this.Invalidate();

            SetColors(false);
            SetLightnessSliderPosition(e.Color);
            SetAlphaSliderPosition(e.Color);

            if (this.SelectedColorChanged != null)
            {
                this.SelectedColorChanged(this, e);
            }
        }


        /// <summary>
        /// The starting lightness is dependant on the lightness of the colorgroup in relation to the lightness of the selected color.
        /// </summary>
        /// <param name="color"></param>
        private void SetLightnessSliderPosition(Color color)
        {
            if (color.IsEmpty)
            {
                trkLightness.Value = 0;
                return;
            }

            double hue, saturation, value;
            ColorHelper.HSVFromRGB(color, out hue, out saturation, out value);

            int half = (int)(trkLightness.Maximum / 2);
            if (this.Color.R == this.Color.G && this.Color.G == this.Color.B)
            {
                value = (double)this.Color.R / 255;
                trkLightness.Value = (int)(value * (half - _LIGHTNESS_FORGIVENESS));
            }
            else if (saturation > value)
            {
                trkLightness.Value = (int)(value * (half - _LIGHTNESS_FORGIVENESS));
            }
            else
            {
                trkLightness.Value = -1 * (int)(saturation * half - trkLightness.Maximum);
            }

            trkLightness_ValueChanged(this, new EventArgs());
            LightnessColor = color;
        }


        private void SetAlphaSliderPosition(Color color)
        {
            if (color.IsEmpty)
            {
                trkLightness.Value = 0;
                return;
            }

            trkAlpha.Value = color.A;
            SetColors(false);
        }

         /// <summary>
         /// Horizontal positioning of the wheel in the control.
         /// 
         /// The Control can be rectangular, but most of the wheels require a square area.
         /// This determines the positioning of the wheel (left, center, right).
         /// </summary>
        public HorizontalAlignment AlignmentOfWheel
        {
            get { return cw.Alignment; }
            set { cw.Alignment = value; }
        }

        /// <summary>
        /// The Current Selected Color 
        /// </summary>
        public Color Color
        {
            get 
            {
                return this.AlphaColor;
            }
            set { cw.Color = value; }
        }

        private Color LightnessColor { get; set; }

        private Color AlphaColor { get; set; }

        /// <summary>
        /// Gets or Sets whether the selector ring should be visible on the color wheel
        /// </summary>
        public bool ShowSelectorOnColorWheel
        {
            get { return cw.ShowSelector; }
            set { cw.ShowSelector = value; }
        }

         /// <summary>
         /// Repositions panels 
         /// </summary>
         /// <param name="e"></param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            pnlAlpha.Top = pnlLightness.Top =  lblAlpha.Bottom + _BUFFER;
            trkAlpha.Top =  trkLightness.Top = pnlLightness.Top - _SLIDER_TOP_OFFSET;
            pnlAlpha.Height = pnlLightness.Height = pnlLightness.Height = (pnlRGB.Top - pnlLightness.Top - _BUFFER) > 0 ? (pnlRGB.Top - pnlLightness.Top - _BUFFER) : 1;
            trkAlpha.Height = trkLightness.Height = pnlLightness.Height + _SLIDER_HEIGHT_OFFSET;

            int x = (pnlRGB.Width - pnlSelected.Width - pnlRGBHEX.Width) / 2;
            x = x > 0 ? x : 1;
            pnlRGBHEX.Left = x;
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            SetColors(false);
        }

        private void SetAlphaColor()
        {
            if (!LightnessColor.IsEmpty)
            {
                int a = trkAlpha.Value; // I set trkAlpha min = 0, max = 255 to match with color system
                int r = LightnessColor.R;
                int g = LightnessColor.G;
                int b = LightnessColor.B;
                AlphaColor = Color.FromArgb(a, r, g, b);
            }
        }

        void SetColors(bool resetSliders)
        {
            SetAlphaColor(); // set AlphaColor Property first
            if (this.Color.IsEmpty) { this.AlphaColor = Color.White; }


            //Only set the text boxes when user is NOT typing in them
            if (!_suppressTextChanged)
            {
                try
                {
                    //Suppress the text changed events
                    _suppressTextChanged = true;

                    txtR.Text = this.Color.R.ToString();
                    txtG.Text = this.Color.G.ToString();
                    txtB.Text = this.Color.B.ToString();
                    txtA.Text = this.Color.A.ToString();
                    txtHex.Text = ColorTranslator.ToHtml(Color.FromArgb(this.Color.A, this.Color.R, this.Color.G, this.Color.B));
                    double h, s, v;
                    ColorHelper.HSVFromRGB(this.Color, out h, out s, out v);
                    double h2, s2, v2;
                    ColorHelper.HSVFromRGB(cw.ColorGroup, out h2, out s2, out v2);

                    txtH.Text = string.Format("{0:0.00}", h2); // use hue from colorGroup, to fix the hsvtriangle hue value jumping issue
                    txtS.Text = string.Format("{0:0.00}",s);
                    txtV.Text = string.Format("{0:0.00}", v);
                }
                finally
                {
                    _suppressTextChanged = false;
                }
            }

            pnlLightness.Invalidate();
            pnlAlpha.Invalidate();
            pnlSelectedColor.Invalidate();

            if (resetSliders)
            {
                trkLightness.Value = (int)(trkLightness.Maximum / 2);
                trkAlpha.Value = trkAlpha.Maximum;
            }
        }

        /// <summary>
        /// Places the Color Settings panel on either the Right or Left sides.
        /// 
        /// Center is not a valid placement and is ignored.
        /// </summary>
        public HorizontalAlignment SettingsAlignment
        {
            get
            {
                if (pnlAlphaSlider.Dock == DockStyle.Right) { return HorizontalAlignment.Right; }
                if (pnlAlphaSlider.Dock == DockStyle.Left) { return HorizontalAlignment.Left; }
                return HorizontalAlignment.Right;
            }
            set
            {
                if (value == HorizontalAlignment.Center) { return; }

                if (value == HorizontalAlignment.Left)
                {
                    pnlAlphaSlider.Dock = DockStyle.Left;
                    pnlLightSlider.Dock = DockStyle.Left;
                    return;
                }

                if (value == HorizontalAlignment.Right)
                {
                    pnlAlphaSlider.Dock = DockStyle.Right;
                    pnlLightSlider.Dock = DockStyle.Right;
                    return;
                }
            }
        }




        #region PassThroughEvents

        void cw_MouseWheel(object sender, MouseEventArgs e)
        {
            this.OnMouseWheel(e);
        }

        void cw_MouseUp(object sender, MouseEventArgs e)
        {
            this.OnMouseUp(e);
        }

        void cw_MouseMove(object sender, MouseEventArgs e)
        {
            this.OnMouseMove(e);
        }



        void cw_MouseHover(object sender, EventArgs e)
        {
            this.OnMouseHover(e);
        }


        void cw_MouseDown(object sender, MouseEventArgs e)
        {
            this.OnMouseDown(e);
        }

        void cw_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            this.OnMouseDoubleClick(e);
        }

        void cw_MouseClick(object sender, MouseEventArgs e)
        {
            this.OnMouseClick(e);
        }

        #endregion

        private void trkSlider_Scroll(object sender, EventArgs e)
        {

        }

        private void pnlRGB_Paint(object sender, PaintEventArgs e)
        {

        }
    }
}
